import { Game } from "../Game";
import { ChunkEntityType, GameEvent } from "../GameDef";
import { CmpChunkEntity } from "../ecs/cmp/CmpChunkEntity";
import { CmpFood } from "../ecs/cmp/CmpFood";
import { CmpSnake } from "../ecs/cmp/CmpSnake";
import { CmpSnakeBody } from "../ecs/cmp/CmpSnakeBody";
import { SnakeFactory } from "../ecs/factory/SnakeFactory";
import { SCmpWorld } from "../ecs/scmp/SCmpWorld";
import { Rect } from "../lib/kgame/d2/math/Rect";
import CmpTrans from "../lib/kgame/ecs/CmpTrans";
import { IRect, ISize } from "../lib/kgame/util/Defined";
import { GameConn } from "./GameConn";

/**
 * 游戏玩家
 */
export class GamePlayer {

    /** 蛇组件 */
    snake: CmpSnake
    /** 视野焦点 */
    focus: CmpSnake
    /** 视野 */
    viewRect: IRect = { x: 0, y: 0, width: 0, height: 0 };
    /** 视野里的实体 */
    entityIds = new Set<number>();
    /** 视野里被吃的食物 */
    eatenFoods: kp_entity_eatenFood[] = [];
    lastScore: number;

    constructor(
        /** 游戏全局 */
        public game: Game,
        /** 通信连接 */
        public conn: GameConn,
        /** 玩家id */
        public pid: number,
        /** 玩家名 */
        public name: string,
        /** 屏幕 */
        public screen: ISize
    ) {
        //创建蛇
        this.snake = SnakeFactory.createSnake(game.scene, name);


        game.scene.on(GameEvent.snakeSpawn, data => {
            if (data === this.snake) {
                this.focus = this.snake;
                this.conn.send(KProtoId.focus, { id: this.snake.head.entityId });
            }
        }, this);

        game.scene.on(GameEvent.snakeDead, data => {
            if (data === this.snake) {
                if (this.snake.killer instanceof CmpSnake) {
                    this.focus = this.snake.killer;
                    if (!this.focus.death) {
                        this.conn.send(KProtoId.focus, { id: this.focus.head.entityId });
                    }
                }
            }
        }, this);

        game.scene.on(GameEvent.foodEaten, data => {
            data.forEach(food => {
                if (this.entityIds.has(food.id) && this.entityIds.has(food.eaterId)) {
                    this.eatenFoods.push(food);
                }
            });
        }, this);

        this.conn.on(KProtoId.updateScreen, data => {
            Rect.setSizeTo(this.screen, data.screen);
        });

        this.conn.on(KProtoId.ctrlRot, data => {
            if (!this.snake.death) {
                this.snake.head.move.toDir = data.rot;
            }
        });
        this.conn.on(KProtoId.ctrlSprint, data => {
            if (!this.snake.death) {
                this.snake.head.isSprint = data.isSprint;
            }
        });

        //发送当前场景数据
        const snakes: kp_snake_snake[] = [];
        game.snakeMap.forEach(snake => {
            if (!snake.death) {
                snakes.push(game.getSnakeInfo(snake));
            }
        });
        this.conn.send(KProtoId.snakeInfo, {
            type: 1,
            snakes
        });
        this.conn.send(KProtoId.msg_add, {
            msgs: game.msgs
        });
    }

    onRemove() {
        this.snake.doRemove = true;
        this.snake = null;
        this.game.scene.offCaller(this);
        this.game = null;
        this.focus = null;
    }

    onSnakeAdd(snakes: kp_snake_snake[]) {
        this.conn.send(KProtoId.snakeInfo, {
            type: 1,
            snakes
        });
    }

    onSnakeUpdate(snakes: kp_snake_snake[]) {
        this.conn.send(KProtoId.snakeInfo, {
            type: 2,
            snakes
        });
    }

    update() {

        const removeIds: number[] = [];
        const addSnakeMap = new Map<CmpSnake, kp_entity_snake>();
        const updateSnakeMap = new Map<CmpSnake, kp_entity_snake>();
        const addFoods: kp_entity_food[] = [];
        const updateFoods: kp_entity_food[] = [];

        const world = this.snake.scene.getCmp(SCmpWorld);
        this.updateViewRect();
        const rect = this.viewRect;

        const entityMap = new Map<number, CmpChunkEntity>();
        const entities = world.chunk.getCoverItems(rect);
        entities.forEach(entity => entityMap.set(entity.entityId, entity));

        this.entityIds.forEach(id => {
            const entity = entityMap.get(id);
            if (entity) {
                switch (entity.type) {
                    case ChunkEntityType.snakeBody:
                        const body = entity.getCmp(CmpSnakeBody);
                        let kpSnake = updateSnakeMap.get(body.head.snake);
                        if (kpSnake) {
                            kpSnake.bodys.push(this.getSnakeBody(body));
                        } else {
                            updateSnakeMap.set(body.head.snake, {
                                id: body.head.snake.entityId,
                                size: body.head.size,
                                bodys: [this.getSnakeBody(body)]
                            });
                        }
                        break;
                    case ChunkEntityType.food:
                        // updateFoods.push(this.getEntity(entity));
                        break;
                }
            } else {
                this.entityIds.delete(id);
                removeIds.push(id);
            }
        });

        entities.forEach(entity => {
            if (!this.entityIds.has(entity.entityId)) {
                this.entityIds.add(entity.entityId);

                switch (entity.type) {
                    case ChunkEntityType.snakeBody:
                        const body = entity.getCmp(CmpSnakeBody);
                        let kpSnake = addSnakeMap.get(body.head.snake);
                        if (kpSnake) {
                            kpSnake.bodys.push(this.getSnakeBody(body));
                        } else {
                            addSnakeMap.set(body.head.snake, {
                                id: body.head.snake.entityId,
                                size: body.head.size,
                                bodys: [this.getSnakeBody(body)]
                            });
                        }
                        break;
                    case ChunkEntityType.food:
                        addFoods.push(this.getFood(entity));
                        break;
                }
            }
        });

        if (addSnakeMap.size > 0 || addFoods.length > 0) {
            this.conn.send(KProtoId.entityAdd, {
                snakes: addSnakeMap.size > 0 ? Array.from(addSnakeMap.values()) : undefined,
                foods: addFoods
            });
        }

        if (updateSnakeMap.size > 0 || updateFoods.length > 0) {
            this.conn.send(KProtoId.entityUpdate, {
                snakes: updateSnakeMap.size > 0 ? Array.from(updateSnakeMap.values()) : undefined,
                foods: updateFoods
            });
        }

        if (this.eatenFoods.length > 0) {
            this.conn.send(KProtoId.foodEaten, { foods: this.eatenFoods });
            this.eatenFoods.length = 0;
        }

        if (removeIds.length > 0) {
            this.conn.send(KProtoId.entityRemove, { ids: removeIds });
        }

        if (!this.snake.death && this.snake.head.score !== this.lastScore) {
            const { score } = this.snake.head;
            this.lastScore = score;
            this.onSnakeUpdate([{ id: this.snake.entityId, score }]);
        }

    }

    private updateViewRect() {
        if (this.focus?.head) {
            const { width, height } = this.screen;
            const size = this.focus.head.size * 25;
            const scale = Math.sqrt((size * size) / (width * height));
            this.viewRect.width = this.screen.width * scale;
            this.viewRect.height = this.screen.height * scale;
            this.viewRect.x = this.focus.pos.x - this.viewRect.width / 2;
            this.viewRect.y = this.focus.pos.y - this.viewRect.height / 2;
        }
    }

    private getFood(e: CmpChunkEntity): kp_entity_food {
        return {
            id: e.entityId,
            x: e.pos.x,
            y: e.pos.y,
            rot: 0,
            size: e.aabb.width,
            color: e.getCmp(CmpFood).color
        }
    }

    private getSnakeBody(e: CmpSnakeBody): kp_entity_snakeBody {
        return {
            id: e.entityId,
            idx: e.idx,
            x: e.pos.x,
            y: e.pos.y,
            rot: e.getCmp(CmpTrans).rot
        }
    }

}